package cn.kennylee.codehub.springboot.lifecycle.logger;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.system.SystemUtil;
import jakarta.servlet.ServletContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.web.context.WebServerApplicationContext;
import org.springframework.boot.web.server.WebServer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;

import java.time.ZoneId;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;

import static cn.hutool.core.util.RuntimeUtil.getUsableMemory;
import static cn.hutool.system.SystemUtil.*;

/**
 * 应用生命周期事件日志记录器.
 *
 * <p>监听并记录应用启动、关闭及致命异常事件，包括：</p>
 * <ul>
 *   <li>启动成功时打印环境信息</li>
 *   <li>优雅停机时记录关闭事件</li>
 *   <li>OOM等致命错误发生时输出异常日志</li>
 * </ul>
 * <p>注：调用 kill -9 无日志（JVM 强制终止，无法捕获）</p>
 * <p>技术实现：</p>
 * <ul>
 *   <li>通过 {@link EventListener} 监听Spring生命周期事件</li>
 *   <li>使用 {@link Runtime#addShutdownHook} 处理JVM关闭事件</li>
 *   <li>通过全局异常处理器捕获 {@link OutOfMemoryError}</li>
 * </ul>
 *
 * @author kennylee
 * @see ApplicationReadyEvent  应用启动完成事件
 * @see ContextClosedEvent      优雅停机触发事件
 * <p>Created on 2025/3/18.</p>
 * @since 0.0.1
 */
@Slf4j
public class AppLifecycleLogger {

    private static final AtomicBoolean SHUTDOWN_HOOK_REGISTERED = new AtomicBoolean(false);
    private static boolean shutdownLogged = false;

    private static final AtomicBoolean OOM_TRIGGERED = new AtomicBoolean(false);

    private static final String PAD_STR = "=";
    private static final int LINE_LENGTH = 75;

    private final Environment environment;

    /**
     * 是否已触发 OOM
     */
    public AppLifecycleLogger(Environment environment) {
        this.environment = environment;
        registerGlobalExceptionHandler();
        registerShutdownHook();

        // 确保全局只注册一次关闭钩子
        if (SHUTDOWN_HOOK_REGISTERED.compareAndSet(false, true)) {
            Runtime.getRuntime().addShutdownHook(new Thread(this::logShutdownIfNeeded));
        }
    }

    /**
     * 监听应用启动完成事件，输出环境信息.
     */
    @EventListener(ApplicationReadyEvent.class)
    @Order
    public void logStartupInfo() {
        if (log.isInfoEnabled()) {
            log.info(new ApplicationInfo().toString());
        }
    }

    /**
     * 监听优雅停机事件
     */
    @EventListener(ContextClosedEvent.class)
    public void logGracefulShutdown(ContextClosedEvent event) {
        // 仅处理根上下文关闭事件
        if (event.getApplicationContext().getParent() == null) {
            logShutdownIfNeeded();
        }
    }

    private synchronized void logShutdownIfNeeded() {
        if (!shutdownLogged) {
            log.info("|----------------------------------------|");
            log.info("| 【{}服务】正在优雅停机...                     ", getAppName());
            log.info("|----------------------------------------|");
            shutdownLogged = true;
        }
    }

    /**
     * 注册全局异常处理器（捕获 OOM）
     */
    private void registerGlobalExceptionHandler() {
        Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
            if (throwable instanceof OutOfMemoryError) {
                OOM_TRIGGERED.set(true);
                log.error("|----------------------------------------|");
                log.error("| 【{}服务】检测到致命错误: OOM，应用即将终止         ", getAppName());
                log.error("| 错误信息: {}                            ", throwable.getMessage());
                log.error("|----------------------------------------|");
                log.error(throwable.getMessage(), throwable);
            }
        });
    }

    /**
     * 注册 JVM 关闭钩子（处理 OOM 终止）
     */
    private void registerShutdownHook() {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            if (OOM_TRIGGERED.get()) {
                log.error("|----------------------------------------|");
                log.error("| 【{}服务】因 OOM 异常终止                      ", getAppName());
                log.error("|----------------------------------------|");
            }
        }));
    }

    private class ApplicationInfo {
        private static final String SERVER_TYPE_TOMCAT = "Tomcat";
        private static final String SERVER_TYPE_JETTY = "Jetty";
        private static final String SERVER_TYPE_NETTY = "Netty";
        private static final String SERVER_TYPE_UNDERTOW = "Undertow";

        private void addJavaRuntimeMemoryInfo() {
            log.info(StrUtil.center(" Java Runtime Memory ", LINE_LENGTH, PAD_STR) +
                    "\nMax Memory: {}\nTotal Memory: {}\nFree Memory: {}\nUsable Memory: {}",
                FileUtil.readableFileSize(getMaxMemory()),
                FileUtil.readableFileSize(getTotalMemory()),
                FileUtil.readableFileSize(getFreeMemory()),
                FileUtil.readableFileSize(getUsableMemory()));
        }

        /**
         * 解析服务器类型（无需引入具体依赖）
         */
        private String parseServerType(String className) {
            if (StrUtil.contains(className, SERVER_TYPE_NETTY)) {
                return SERVER_TYPE_NETTY;
            }
            if (StrUtil.contains(className, SERVER_TYPE_TOMCAT)) {
                return SERVER_TYPE_TOMCAT;
            }
            if (StrUtil.contains(className, SERVER_TYPE_JETTY)) {
                return SERVER_TYPE_JETTY;
            }
            if (StrUtil.contains(className, SERVER_TYPE_UNDERTOW)) {
                return SERVER_TYPE_UNDERTOW;
            }
            return "Unknown";
        }

        /**
         * 安全获取服务器版本（通过反射避免类依赖）
         */
        private String getServerVersion(String serverType) {
            try {
                return switch (serverType) {
                    case SERVER_TYPE_NETTY -> getVersionSafely("io.netty.util.Version", "netty-common");
                    case SERVER_TYPE_UNDERTOW -> getVersionSafely("io.undertow.Version", "Undertow");
                    case SERVER_TYPE_TOMCAT -> getVersionSafely("org.apache.catalina.util.ServerInfo", "Tomcat");
                    default -> "N/A";
                };
            } catch (Exception e) {
                return "N/A";
            }
        }

        /**
         * 通过类名和方法名反射获取版本
         */
        private String getVersionSafely(String className, String type) {
            try {
                Class<?> clazz = Class.forName(className);
                if ("netty-common".equals(type)) {
                    Object version = clazz.getMethod("identify").invoke(null);
                    return (String) version.getClass().getMethod("get", String.class).invoke(version, type);
                } else if (SERVER_TYPE_UNDERTOW.equals(type)) {
                    return (String) clazz.getMethod("getVersionString").invoke(null);
                } else if (SERVER_TYPE_TOMCAT.equals(type)) {
                    return (String) clazz.getMethod("getServerInfo").invoke(null);
                }
                return "N/A";
            } catch (Exception e) {
                return "N/A";
            }
        }

        /**
         * 判断是否为 Servlet 环境
         */
        private boolean isServletEnv(ApplicationContext context) {
            try {
                Class.forName("javax.servlet.ServletContext");
                return context.containsBean("servletContext");
            } catch (ClassNotFoundException e) {
                return false;
            }
        }

        /**
         * 获取 Servlet 版本
         */
        private String getServletVersion(ApplicationContext context) {
            try {
                ServletContext servletContext = context.getBean(ServletContext.class);
                return servletContext.getMajorVersion() + "." + servletContext.getMinorVersion();
            } catch (Exception e) {
                return "N/A";
            }
        }

        @Override
        public String toString() {
            if (log.isInfoEnabled()) {
                StringBuilder sb = new StringBuilder("应用信息\n");

                // Web服务器信息
                sb.append(StrUtil.center(" WebServer Info ", LINE_LENGTH, PAD_STR)).append("\n");
                ApplicationContext context = SpringUtil.getApplicationContext();
                ObjectProvider<ServletContext> servletContextProvider = context.getBeanProvider(ServletContext.class);
                ServletContext servletContext = servletContextProvider.getIfAvailable();

                // 判断是否为 Servlet 环境
                if (Objects.nonNull(servletContext)) {
                    sb.append("Web Server: ").append(servletContext.getServerInfo()).append("\n")
                        .append("Servlet Specification Version: ").append(servletContext.getMajorVersion())
                        .append(".").append(servletContext.getMinorVersion()).append("\n");
                }
                // Reactive 环境
                else if (context instanceof WebServerApplicationContext) {
                    WebServer webServer = ((WebServerApplicationContext) context).getWebServer();
                    String serverType = parseServerType(webServer.getClass().getName());
                    String version = getServerVersion(serverType);

                    sb.append("Web Server: ").append(serverType).append(" (").append(version).append(")\n")
                        .append("Servlet Specification Version: ")
                        .append(isServletEnv(context) ? getServletVersion(context) : "N/A (Reactive)").append("\n");
                } else {
                    sb.append("非 Web 环境\n");
                }

                sb.append(StrUtil.center(" Java Info ", LINE_LENGTH, PAD_STR)).append("\n");

                // CPU核数信息
                int cpuCores = Runtime.getRuntime().availableProcessors();
                sb.append("CPU Cores: ").append(cpuCores).append("\n");

                // Java运行时信息
                sb.append("Java Version: ").append(SystemUtil.getJavaInfo().getVersion()).append("\n")
                    .append("Java Runtime: ").append(SystemUtil.getJavaRuntimeInfo().getName())
                    .append(" (build ").append(SystemUtil.getJavaRuntimeInfo().getVersion()).append(")\n")
                    .append("Java VM: ").append(SystemUtil.getJvmInfo().getName())
                    .append(" (build ").append(SystemUtil.getJvmInfo().getVersion())
                    .append(", ").append(SystemUtil.getJvmInfo().getInfo()).append(")\n")
                    .append("Default Time Zone: ").append(ZoneId.systemDefault()).append("\n");

                // 内存信息
                sb.append(StrUtil.center(" Java Runtime Memory ", LINE_LENGTH, PAD_STR)).append("\n")
                    .append("Max Memory: ").append(FileUtil.readableFileSize(getMaxMemory())).append("\n")
                    .append("Total Memory: ").append(FileUtil.readableFileSize(getTotalMemory())).append("\n")
                    .append("Free Memory: ").append(FileUtil.readableFileSize(getFreeMemory())).append("\n")
                    .append("Usable Memory: ").append(FileUtil.readableFileSize(getUsableMemory())).append("\n");

                sb.append(StrUtil.center("", LINE_LENGTH, PAD_STR));
                log.info(sb.toString());
            }
            return StrUtil.center(
                StrUtil.format(" [{}服务]已启动成功, 当前环境: {}", getAppName(), getActiveProfiles()),
                LINE_LENGTH, PAD_STR);
        }
    }

    private String getActiveProfiles() {
        return String.join(",", environment.getActiveProfiles());
    }

    private String getAppName() {
        return environment.getProperty("spring.application.name", "");
    }
}
